from absl import app, flags
import copy
import pandas as pd

from xunkemgmt_client.client.util.query_util import (
  query_exchange_apis, query_strategies, query_accounts, query_applications)
from xunkemgmt_client.tool.slack_noti import send_to_slack

FLAGS = flags.FLAGS

TABLES = [
    'account', 'account_group', 'application', 'exchange_api', 'strategy', 'strategy_group'
]


class AccountBusinessUnitError:
  pass


class AccountGroupError:
  pass


class FtxAccountDuplicates:
  pass


class OkexAccountDuplicates:
  pass


class OutdatedAccountName:
  pass


class WrongStrategyName:
  pass


class ApplicationBusinessUnitError:
  pass


class ApplicationAccountGroupError:
  pass


class CoinFOFAccountNotInApplication:
  pass


class CoinFOFAccountNotConsistent:
  pass


def check_account_meta_data(account_df):
  account_df = account_df[[
      'account_id', 'account_group', 'market_type', 'exchange', 'owner', 'business_unit', 'active'
  ]]
  total_alarm_info = {}
  # accounts with same (exchange, owner) should have same business_unit
  alarm = AccountBusinessUnitError
  abnormal_account_ids = []
  account_df_temp = copy.deepcopy(account_df)
  account_df_temp['owner'] = account_df_temp['owner'].apply(lambda x: x.replace('_swap', ''))
  account_df_temp = account_df_temp.set_index(['exchange', 'owner'])
  account_df_temp.sort_index(inplace=True)
  for index in account_df_temp.index:
    business_unit_df = account_df_temp.loc[index, ['business_unit']]
    if len(business_unit_df.drop_duplicates()) > 1:
      abnormal_account_ids.extend(account_df_temp.loc[[index], 'account_id'].tolist())
  total_alarm_info[alarm] = account_df[account_df['account_id'].isin(abnormal_account_ids)]

  # accounts with same (exchange, owner) should have same account_group
  alarm = AccountGroupError
  abnormal_account_ids = []
  account_df_temp = copy.deepcopy(account_df)
  account_df_temp['owner'] = account_df_temp['owner'].apply(lambda x: x.replace('_swap', ''))
  account_df_temp = account_df_temp.set_index(['exchange', 'owner'])
  account_df_temp.sort_index(inplace=True)
  for index in account_df_temp.index:
    account_group_df = account_df_temp.loc[index, ['account_group']]
    if len(account_group_df.drop_duplicates()) > 1:
      abnormal_account_ids.extend(account_df_temp.loc[[index], 'account_id'].tolist())
  total_alarm_info[alarm] = account_df[account_df['account_id'].isin(abnormal_account_ids)]

  # FTX accounts with same owner should not have both spot and futures
  alarm = FtxAccountDuplicates
  abnormal_account_ids = []
  account_df_temp = account_df[account_df['exchange'] == 'Ftx'].set_index('owner')
  for index in account_df_temp.index:
    market_type_df = account_df_temp.loc[index, ['market_type']]
    if len(market_type_df) > 1:
      abnormal_account_ids.extend(account_df_temp.loc[[index], 'account_id'])
  total_alarm_info[alarm] = account_df[account_df['account_id'].isin(abnormal_account_ids)]

  # Okex accounts with same owner should not have both spot and futures
  alarm = OkexAccountDuplicates
  abnormal_account_ids = []
  account_df_temp = account_df[(account_df['exchange'] == 'Okex')
                               & (account_df['active'])].set_index('owner')
  for index in account_df_temp.index:
    market_type_df = account_df_temp.loc[index, ['market_type']]
    market_type_df = market_type_df.drop_duplicates()
    if len(market_type_df) > 2 or (len(market_type_df) == 2 and 'ExWallet' not in list(market_type_df['market_type'])):
      abnormal_account_ids.extend(account_df_temp.loc[[index], 'account_id'])
  total_alarm_info[alarm] = account_df[account_df['account_id'].isin(abnormal_account_ids)]

  # xunke** account in Okex and Huobi should be inactive
  alarm = OutdatedAccountName
  abnormal_account_ids = []
  account_df_temp = account_df[(account_df['account_group'] == 'Xunke') &
                               (account_df['active'] == 1) &
                               (account_df['exchange'].isin(['Okex', 'Huobi']))].set_index('owner')
  for index in account_df_temp.index:
    if index.startswith('xunke'):
      abnormal_account_ids.extend(account_df_temp.loc[[index], 'account_id'])
  total_alarm_info[alarm] = account_df[account_df['account_id'].isin(abnormal_account_ids)]

  # Coinfof account should have All the market_type in Binance, Huobi and Okex
  alarm = CoinFOFAccountNotConsistent
  account_df_temp = account_df[(account_df['exchange'].isin(['Binance', 'Okex', 'Huobi'])) &
                               (account_df['business_unit'] == 'CoinFOF')].set_index(
                                   ['exchange', 'owner'])
  account_df_temp.sort_index(inplace=True)
  alarm_msg = []
  for index in account_df_temp.index:
    exchange, owner = index
    if exchange == 'Binance':
      market_type_universe = ['Spot', 'Margin', 'Futures']
    elif exchange == 'Okex':
      market_type_universe = ['Spot', 'Futures', 'Margin', 'ExWallet']
    elif exchange == 'Huobi':
      market_type_universe = ['Spot', 'Futures', 'Margin']
    if owner.endswith('_swap'):
      owner_master = owner.split('_')[0]
      owner_swap = owner
    else:
      owner_master = owner
      owner_swap = owner + '_swap'
    market_type_list = account_df_temp.loc[[(exchange, owner_master), (exchange, owner_swap)],
                                           'market_type'].tolist()
    missed_market_type = [x for x in market_type_universe if x not in market_type_list]
    alarm_msg.extend([(market_type, exchange, owner_master) for market_type in missed_market_type])
    if exchange == 'Huobi' and (exchange, owner_master + '_linear_swap') not in account_df_temp.index:
      alarm_msg.append(('Futures', exchange, owner_master + '_linear_swap'))
    if (exchange, owner_swap) not in account_df_temp.index:
      alarm_msg.append(('Futures', exchange, owner_swap))
    alarm_df = pd.DataFrame(alarm_msg, columns=['market_type', 'exchange',
                                                'owner']).drop_duplicates()
    alarm_df.index.name = 'missed'
    total_alarm_info[alarm] = alarm_df

  return total_alarm_info


def check_strategy_meta_data(strategy_df):
  strategy_df = strategy_df[[
      'strategy_id', 'strategy_group', 'strategy_name', 'trader', 'business_unit', 'active'
  ]]
  total_alarm_info = {}
  # name of active strategy should start with strategy_group_name
  alarm = WrongStrategyName
  abnormal_strategy_ids = []
  strategy_df_temp = strategy_df[strategy_df['active'] == 1].set_index('strategy_name')
  for index in strategy_df_temp.index:
    strategy_group = strategy_df_temp.loc[index, 'strategy_group']
    strategy_group = '_'.join(strategy_group.split('-'))
    if not index.startswith(strategy_group):
      abnormal_strategy_ids.extend(strategy_df_temp.loc[[index], 'strategy_id'].tolist())
  total_alarm_info[alarm] = strategy_df[strategy_df['strategy_id'].isin(abnormal_strategy_ids)]

  return total_alarm_info


def check_application_meta_data(application_df, account_df, strategy_df):
  application_df['account_group'] = application_df['account_id'].apply(
      lambda x: account_df.set_index('account_id').loc[x, 'account_group'])
  application_df['biz_unit_account'] = application_df['account_id'].apply(
      lambda x: account_df.set_index('account_id').loc[x, 'business_unit'] if pd.notnull(x) else 'NULL')
  application_df['biz_unit_strategy'] = application_df['strategy_id'].apply(
      lambda x: strategy_df.set_index('strategy_id').loc[x, 'business_unit'] if pd.notnull(x) else 'NULL')
  total_alarm_info = {}
  # business_unit of account and strategy should be same
  alarm = ApplicationBusinessUnitError
  abnormal_application_ids = []
  application_df_temp = application_df.set_index(['account_id', 'strategy_id'])
  for index in application_df_temp.index:
    account_id, strategy_id = index
    if pd.isnull(account_id) or pd.isnull(strategy_id):
      continue
    account_biz_unit = account_df.set_index('account_id').loc[account_id, 'business_unit']
    strategy_biz_unit = strategy_df.set_index('strategy_id').loc[strategy_id, 'business_unit']
    assert isinstance(account_biz_unit, str), account_biz_unit
    assert isinstance(strategy_biz_unit, str), strategy_biz_unit
    if account_biz_unit != strategy_biz_unit:
      abnormal_application_ids.extend(application_df_temp.loc[[index], 'application_id'].tolist())
  total_alarm_info[alarm] = application_df[[
      'application_id', 'market_type', 'exchange', 'owner', 'strategy_name', 'biz_unit_account',
      'biz_unit_strategy'
  ]][application_df['application_id'].isin(abnormal_application_ids)]

  # accounts of same strategy should be in same account group
  alarm = ApplicationAccountGroupError
  abnormal_application_ids = []
  application_df_temp = application_df.set_index(['strategy_id'])
  for index in application_df_temp.index:
    if pd.isnull(index):
      continue
    account_ids = application_df_temp.loc[[index], 'account_id'].tolist()
    account_group_df = account_df['account_group'][account_df['account_id'].isin(account_ids)]
    if len(account_group_df.drop_duplicates()) > 1:
      abnormal_application_ids.extend(application_df_temp.loc[[index], 'application_id'].tolist())
  total_alarm_info[alarm] = application_df[[
      'application_id', 'strategy_name', 'market_type', 'exchange', 'owner', 'account_group'
  ]][application_df['application_id'].isin(abnormal_application_ids)]

  # active coinfof accounts should all be in application
  alarm = CoinFOFAccountNotInApplication
  account_df_temp = account_df[(account_df['business_unit'] == 'CoinFOF') &
                               (account_df['active'] == 1)]
  total_alarm_info[alarm] = account_df_temp[[
      'account_id', 'market_type', 'exchange', 'owner', 'api_version', 'account_group', 'active'
  ]][~account_df_temp['account_id'].isin(application_df['account_id'].tolist())]

  return total_alarm_info


def check_meta_data(account_df, application_df, strategy_df, *, slack_receiver=None):
  alarm_info_account = check_account_meta_data(account_df)
  alarm_info_strategy = check_strategy_meta_data(strategy_df)
  alarm_info_application = check_application_meta_data(application_df, account_df, strategy_df)
  total_alarm_info = {**alarm_info_account, **alarm_info_strategy, **alarm_info_application}
  msg = ""
  for alarm, alarm_df in total_alarm_info.items():
    if not alarm_df.empty:
      msg += alarm.__name__
      msg += '\n'
      msg += alarm_df.to_string()
      msg += '\n\n'
  print(msg)
  if slack_receiver and msg:
    send_to_slack(msg, slack_receiver, 'file')
  return

def main(_):
  pd.set_option('display.width', 1000)
  pd.set_option('display.max_rows', None)
  pd.set_option('display.max_columns', None)
  pd.set_option('max_colwidth', 200)

  exchange_api_df = query_exchange_apis()
  strategy_df = query_strategies()
  account_df = query_accounts()
  application_df = query_applications()

  if not any(col in ['market_type', 'exchange', 'owner']
    for col in application_df.columns):
    application_df.rename(columns={
                          'market_type_acct': 'market_type',
                          'exchange_acct': 'exchange',
                          'owner_acct': 'owner'}, inplace=True)
  application_df = application_df[['application_id', 'strategy_id', 'account_id',
    'strategy_name', 'market_type', 'exchange', 'api_version', 'owner']]
  account_group_df = account_df[
    ['account_group_id', 'account_group']].drop_duplicates()
  strategy_group_df = strategy_df[
    ['strategy_group_id', 'strategy_group']].drop_duplicates()

  for table in TABLES:
    print("TABLE %s: \n" % table)
    try:
      assert '%s_df' % table in locals(), '%s_df'
      print(locals()['%s_df' % table].sort_values(['%s_id' % table]).to_csv(index=False))
      print("\n\n")
    except Exception as e:
      print(type(e), e)

  check_meta_data(account_df, application_df, strategy_df, slack_receiver=FLAGS.slack_receiver)


if __name__ == '__main__':
  flags.DEFINE_string('slack_receiver', None, 'slack channel to receive msg')
  app.run(main)
